﻿using System;
using System.Collections.Generic;
using System.Text;
using KaroEngine.Exceptions;

namespace KaroEngine
{
    #region Player
    public enum Player
    {
        White,
        Red
    }
    #endregion

    #region Direction
    enum Direction
    {
        Center = 0,
        InnerTopLeft = -22,
        InnerTop = -21,
        InnerTopRight = -20,
        InnerLeft = -1,
        InnerRight = 1,
        InnerBottomLeft = 20,
        InnerBottom = 21,
        InnerBottomRight = 22,
        OuterTopLeft = -44,
        OuterTop = -42,
        OuterTopRight = -40,
        OuterLeft = -2,
        OuterRight = 2,
        OuterBottomLeft = 40,
        OuterBottom = 42,
        OuterBottomRight = 44
    }
    #endregion

    public class Board
    {
        private List<Tile> tiles;
        private Player currentPlayer;
        private List<Move> possibleMoves;
        private List<short> movableTiles;
        private List<short> playTiles;
        private short evaluation;
        private Stack<Move> moveHistory;
        private short numberOfConesOnBoard;
        private bool hasToBeFlipped;
        private int zobristHash;

        #region Getters and Setters
        public List<Tile> Tiles
        {
            get { return tiles; }
        }

        public Player CurrentPlayer
        {
            get { return currentPlayer; }
        }
        public Player OppositePlayer
        {
            get { return CurrentPlayer == Player.White ? Player.Red : Player.White; }
        }

        public bool HasToBeFlipped
        {
            get { return hasToBeFlipped; }
        }

        public short NumberOfConesOnBoard { get { return numberOfConesOnBoard; } set { numberOfConesOnBoard = value; }}

        public void SetTile(short index, Tile tile)
        {
            if (index < 0 || index > Engine.width * Engine.height - 1)
                return;

            tiles[index] = tile;

            // Determine number of free sides of current tile
            DetermineFreeSides(index);

            // Determine nr of free sides for his direct neighbours (just top, right, bottom, left)
            DetermineFreeSides(GetNeighbour(index, Direction.InnerTop));
            DetermineFreeSides(GetNeighbour(index, Direction.InnerRight));
            DetermineFreeSides(GetNeighbour(index, Direction.InnerBottom));
            DetermineFreeSides(GetNeighbour(index, Direction.InnerLeft));
        }

        /// <summary>
        /// Determines free sides of current tile
        /// </summary>
        /// <param name="index"></param>
        private void DetermineFreeSides(short index)
        {
            // If index is not in our tileList, it cannot check anything
            if (index < 0 || index > Engine.width * Engine.height - 1)
                return;

            // If there is no tile at this index, it doesn't have to calc. the nr of neigbours
            if (tiles[index] == null)
                return;

            short indexTopNeigbour = GetNeighbour(index, Direction.InnerTop);
            short indexRightNeigbour = GetNeighbour(index, Direction.InnerRight);
            short indexBottomNeigbour = GetNeighbour(index, Direction.InnerBottom);
            short indexLeftNeigbour = GetNeighbour(index, Direction.InnerLeft);

            tiles[index].FreeSides = 0;

            if (indexTopNeigbour == -1 || tiles[indexTopNeigbour] == null)
            {
                tiles[index].FreeSides++;
            }
            if (indexRightNeigbour == -1 || tiles[indexRightNeigbour] == null)
            {
                tiles[index].FreeSides++;
            }
            if (indexBottomNeigbour == -1 || tiles[indexBottomNeigbour] == null)
            {
                tiles[index].FreeSides++;
            }
            if (indexLeftNeigbour == -1 || tiles[indexLeftNeigbour] == null)
            {
                tiles[index].FreeSides++;
            }
        }

        public int ZobristHash
        {
            get { return zobristHash; }
        }
        #endregion

        public Board()
        {
            tiles = new List<Tile>();

            //Fill the list with null pointers
            for (short i = 0; i < Engine.height; i++)
            {
                for (short j = 0; j < Engine.width; j++)
                {
                    tiles.Add(null);
                }
            }
            this.moveHistory = new Stack<Move>();

            //get the starting row/ column for the first tile to be placed.
            short startingRow = (short)(((Engine.height - Rules.initialBoardHeight) / 2) - 1);
            short startingColumn = (short)(((Engine.width - Rules.initialBoardWidth) / 2) - 1);
            short startingIndex = (short)((startingRow * Engine.width + startingColumn));

            //add the tiles to the List<tile>
            // For each row, count the column from 0 to 4, then reset to 0. Each row add Engine.width to the row index.
            short row = startingIndex;
            for (short column = 0; column < Rules.initialBoardWidth; column++)
            {
                //tiles[column + row] = new Tile();
                SetTile((short)(column + row), new Tile());

                if ((column != 0) &&
                     ((column + 1) % Rules.initialBoardWidth == 0) &&
                     (((row / Engine.width) - 6) < Rules.initialBoardHeight))
                {
                    row += Engine.width;
                    column = -1;
                }
            }

            //setting tiles in the corners as movable ones
            movableTiles = new List<short> { 154, 158, 217, 221 };
            playTiles = new List<short> { 154, 155, 156, 157, 158,
                                          175, 176, 177, 178, 179,
                                          196, 197, 198, 199, 200,
                                          217, 218, 219, 220, 221 };

            currentPlayer = Player.White;

            zobristHash = TranspositionTable.Instance.GetZobristHash(this);
        }

        /// <summary>
        /// Verifies who is the owner of the tile
        /// </summary>
        private bool IsPlayerTile(short i, Player player)
        {
            if (i < 0 || i > Engine.width * Engine.height || tiles[i] == null)
            {
                return false;
            }

            if ((player == Player.Red &&
                    (tiles[i].State == TileState.RedNormal ||
                    tiles[i].State == TileState.RedUpsideDown)) ||

               (player == Player.White &&
                    (tiles[i].State == TileState.WhiteNormal ||
                    tiles[i].State == TileState.WhiteUpsideDown)))
            {
                return true;
            }

            return false;
        }

        /// <summary>
        /// Returns the structure containing indices of all adjacent tiles
        /// </summary>
        public Neighbours GetAdjacentIndices(short tileIndex)
        {
            Neighbours adjacentIndices = new Neighbours() { Origin = tileIndex,
                                                            InnerNeighbours = new List<short>(),
                                                            OuterNeighbours = new List<short>(),
                                                            OwnNeighbours = new List<short>(),
                                                            OpponentNeighbours = new List<short>() };

            List<Direction> directions = new List<Direction>();
            directions.AddRange(new Direction[8] { Direction.InnerLeft,   Direction.InnerTopLeft,
                                                   Direction.InnerTop,    Direction.InnerTopRight,
                                                   Direction.InnerRight,  Direction.InnerBottomRight,
                                                   Direction.InnerBottom, Direction.InnerBottomLeft });

            short innerNeighbourIndex, outerNeighbourIndex;
            foreach (Direction direction in directions)
            {
                innerNeighbourIndex = GetNeighbour(tileIndex, direction);

                if (innerNeighbourIndex == -1) continue;

                if (tiles[innerNeighbourIndex] == null)
                {
                    if (movableTiles.Count > 0)
                    {
                        adjacentIndices.InnerNeighbours.Add(innerNeighbourIndex);
                    }
                }
                else if (tiles[innerNeighbourIndex].State == TileState.Empty)
                {
                    adjacentIndices.InnerNeighbours.Add(innerNeighbourIndex);
                }
                else // inner neighbour is occupied
                {
                    if (currentPlayer == Player.Red)
                    {
                        if (tiles[innerNeighbourIndex].State == TileState.RedNormal ||
                           tiles[innerNeighbourIndex].State == TileState.RedUpsideDown)
                        {
                            adjacentIndices.OwnNeighbours.Add(innerNeighbourIndex);
                        }
                        else
                        {
                            adjacentIndices.OpponentNeighbours.Add(innerNeighbourIndex);
                        }
                    }
                    else if (currentPlayer == Player.White)
                    {
                        if (tiles[innerNeighbourIndex].State == TileState.WhiteNormal ||
                           tiles[innerNeighbourIndex].State == TileState.WhiteUpsideDown)
                        {
                            adjacentIndices.OwnNeighbours.Add(innerNeighbourIndex);
                        }
                        else
                        {
                            adjacentIndices.OpponentNeighbours.Add(innerNeighbourIndex);
                        }
                    }

                    outerNeighbourIndex = GetNeighbour( innerNeighbourIndex, direction );

                    if (outerNeighbourIndex == -1) continue;

                    if (tiles[outerNeighbourIndex] == null)
                    {
                        if (movableTiles.Count > 0)
                        {
                            adjacentIndices.OuterNeighbours.Add(outerNeighbourIndex);
                        }
                    }
                    else if (tiles[outerNeighbourIndex].State == TileState.Empty)
                    {
                        adjacentIndices.OuterNeighbours.Add(outerNeighbourIndex);
                    }
                    else
                    {
                        if (currentPlayer == Player.Red)
                        {
                            if (tiles[outerNeighbourIndex].State == TileState.RedNormal ||
                                tiles[outerNeighbourIndex].State == TileState.RedUpsideDown)
                            {
                                adjacentIndices.OwnNeighbours.Add(outerNeighbourIndex);
                            }
                            else
                            {
                                adjacentIndices.OpponentNeighbours.Add(outerNeighbourIndex);
                            }
                        }
                        else if (currentPlayer == Player.White)
                        {
                            if (tiles[outerNeighbourIndex].State == TileState.WhiteNormal ||
                                tiles[outerNeighbourIndex].State == TileState.WhiteUpsideDown)
                            {
                                adjacentIndices.OwnNeighbours.Add(outerNeighbourIndex);
                            }
                            else
                            {
                                adjacentIndices.OpponentNeighbours.Add(outerNeighbourIndex);
                            }
                        }
                    }
                }
            }

            return adjacentIndices;
        }

        /// <summary>
        /// Calculates the indices of the adjacent tiles depending on the given direction
        /// </summary
        short GetNeighbour(short tileIndex, Direction direction)
        {
            switch (direction)
            {
                case Direction.InnerLeft:
                    if (tileIndex % Engine.width != 0)
                        return (short)(tileIndex - 1);
                    return -1;

                case Direction.InnerTopLeft:
                    if (tileIndex % Engine.width != 0 && tileIndex - Engine.width - 1 > 0)
                        return (short)(tileIndex - Engine.width - 1);
                    return -1;

                case Direction.InnerTop:
                    if (tileIndex - Engine.width > 0)
                        return (short)(tileIndex - Engine.width);
                    return -1;

                case Direction.InnerTopRight:
                    if (tileIndex % Engine.width != Engine.width - 1 && tileIndex - Engine.width + 1 > 0)
                        return (short)(tileIndex - Engine.width + 1);
                    return -1;

                case Direction.InnerRight:
                    if (tileIndex % Engine.width != Engine.width - 1)
                        return (short)(tileIndex + 1);
                    return -1;

                case Direction.InnerBottomRight:
                    if (tileIndex % Engine.width != Engine.width - 1 && tileIndex + Engine.width + 1 < tiles.Count)
                        return (short)(tileIndex + Engine.width + 1);
                    return -1;

                case Direction.InnerBottom:
                    if (tileIndex + Engine.width < tiles.Count)
                        return (short)(tileIndex + Engine.width);
                    return -1;

                case Direction.InnerBottomLeft:
                    if (tileIndex % Engine.width != 0 && tileIndex + Engine.width - 1 < tiles.Count)
                        return (short)(tileIndex + Engine.width - 1);
                    return -1;
            }
            return -1;
        }

        /// <summary>
        /// Evaluates the board situation this method is called on. It detemines how good this situation is for the current
        /// player and returns the value found.
        /// </summary>
        /// <returns>The evaluation value of this situation.</returns>
        public short Evaluate(Player playerToEvaluate)
        {
            evaluation = 0;
            //currentPlayer = OppositePlayer;

            foreach ( short centerIndex in playTiles )
            {
                Tile currentTile = tiles[centerIndex];
                if (currentTile != null)
                {
                    if ((playerToEvaluate == Player.Red && tiles[centerIndex].State == TileState.RedUpsideDown) ||
                         (playerToEvaluate == Player.White && tiles[centerIndex].State == TileState.WhiteUpsideDown))
                    {
                        bool hasNoRows = true;

                        for (short directionIndex = 5; directionIndex < 9; directionIndex++)
                        {
                            Direction direction = GetDirection( directionIndex, true );
                            short newIndex = centerIndex;
                            short rowCounter = CountInARow( direction, ref newIndex );

                            Tile firstTile = tiles[centerIndex - (short) direction];
                            Tile lastTile = tiles[newIndex];
                            Tile beforeFirstTile = tiles[centerIndex - (short) GetDirection( directionIndex, false )];
                            Tile afterLastTile = tiles[newIndex + (short) direction];
                            bool isFirstFree = firstTile == null ? true : firstTile.State == TileState.Empty || firstTile.State == tiles[centerIndex].State;
                            bool isLastFree = lastTile == null ? true : lastTile.State == TileState.Empty;
                            bool isBeforeFirstMine = beforeFirstTile == null ? false : beforeFirstTile.State == tiles[centerIndex].State;
                            bool isAfterLastMine = afterLastTile == null ? false : afterLastTile.State == tiles[centerIndex].State;

                            // If we found a row
                            if (rowCounter > 1)
                            {
                                // Set the first and last tile in a 3-in-a-row with a hole to evaluated.
                                if ( firstTile != null && firstTile.State == TileState.Empty && isBeforeFirstMine )
                                {
                                    beforeFirstTile.Evaluated = true;
                                }
                                if ( isLastFree && isAfterLastMine )
                                {
                                    afterLastTile.Evaluated = true;
                                }
                            }

                            switch ( rowCounter )
                            {
                                case 4:
                                    hasNoRows = false;

                                    evaluation += (short)(25 * 25 + 0.5);
                                    break;
                                case 3:
                                    hasNoRows = false;

                                    // If the row is not blocked on 1 or either side, it is a good/better situation
                                    if ( isFirstFree )
                                    {
                                        evaluation += (short)(7 * 7 + 0.5);
                                    }
                                    if ( isLastFree )
                                    {
                                        evaluation += (short)(7 * 7 + 0.5);
                                    }

                                    // If the row is blocked by both sides, it is very unfortunate
                                    if ( !isFirstFree && !isLastFree )
                                    {
                                        evaluation -= (short)(9 * 9 + 0.5);
                                    }
                                    break;
                                case 2:
                                    hasNoRows = false;

                                    // If the row is not blocked on 1 or either side, it is a good/better situation
                                    if ( isFirstFree )
                                    {
                                        evaluation += (short)(6 * 6 + 0.5 );
                                    }

                                    if (firstTile != null && firstTile.State == TileState.Empty)
                                    {
                                        // If there is a 3 in a row, but there is a gap in the middle, it is a good situation
                                        if ( isBeforeFirstMine )
                                        {
                                            evaluation += (short) ( 12 * 12 + 0.5 );
                                        }
                                    }
                                    if (isLastFree)
                                    {
                                        evaluation += (short)(6 * 6 + 0.5);

                                        // If there is a 3 in a row, but there is a gap in the middle, it is a good situation
                                        if ( isAfterLastMine )
                                        {
                                            evaluation += (short)(12 * 12 + 0.5);
                                        }
                                    }

                                    // If the row is blocked by both sides, it is very unfortunate
                                    if ( !isFirstFree && !isLastFree )
                                    {
                                        evaluation -= (short)(8 * 8 + 0.5);
                                    }

                                    break;
                            }
                        }

                        if ( hasNoRows && !currentTile.Evaluated )
                        {
                            evaluation += (short)(4 * 4 + 0.5);
                        }
                    }
                    else if ((playerToEvaluate == Player.Red && tiles[centerIndex].State == TileState.RedNormal) ||
                              (playerToEvaluate == Player.White && tiles[centerIndex].State == TileState.WhiteNormal))
                    {
                        Neighbours neighbours = GetAdjacentIndices( centerIndex );

                        // If we can flip our Cone the next round:
                        if ( neighbours.OuterNeighbours.Count > 0 )
                        {
                            evaluation += (short)(3 * 3 + 0.5);
                        }
                        // If we have some friendly Cones nearby (to create grouping):
                        if ( neighbours.OwnNeighbours.Count > 2 )
                        {
                            evaluation += (short)(2 * 2 + 0.5);
                        }
                        // If the tile is a corner piece and we're blocking it:
                        if (this.IsCornerTile(centerIndex))
                        {
                            evaluation += (short)(1 * 1 + 0.5);
                        }
                    }
                }
            }

            foreach (short index in playTiles)
            {
                tiles[index].Evaluated = false;
            }

            return evaluation;
        }

        /// <summary>
        /// Returns the Direction enum corrosponding with the direction value passed as a parameter. The direction value will
        /// be considered as follows:
        /// 1 2 3
        /// 4 x 5
        /// 6 7 8
        /// Where x is the center tile of the direction.
        /// 
        /// If the method should return the outer tile in the given direction, the value will be interpreted as follows:
        /// 1  2  3
        /// 
        /// 4  x  5
        /// 
        /// 6  7  8
        /// Where x is the center tile of the direction.
        /// </summary>
        /// <param name="direction">The direction to return as an enum.</param>
        /// <param name="isInnerDirection">A boolean indicating whether to return the inner or outer direction value.</param>
        /// <returns>The Direction enum of the given direction and inner/outer circle.</returns>
        private Direction GetDirection(short direction, bool isInnerDirection)
        {
            switch (direction)
            {
                case 1:
                    return isInnerDirection ? Direction.InnerTopLeft : Direction.OuterTopLeft;
                case 2:
                    return isInnerDirection ? Direction.InnerTop : Direction.OuterTop;
                case 3:
                    return isInnerDirection ? Direction.InnerTopRight : Direction.OuterTopRight;
                case 4:
                    return isInnerDirection ? Direction.InnerLeft : Direction.OuterLeft;
                case 5:
                    return isInnerDirection ? Direction.InnerRight : Direction.OuterRight;
                case 6:
                    return isInnerDirection ? Direction.InnerBottomLeft : Direction.OuterBottomLeft;
                case 7:
                    return isInnerDirection ? Direction.InnerBottom : Direction.OuterBottom;
                case 8:
                    return isInnerDirection ? Direction.InnerBottomRight : Direction.OuterBottomRight;
                default:
                    return Direction.Center;
            }
        }

        /// <summary>
        /// Returns the Direction enum corrosponding with the opposite direction value passed as a parameter. The direction
        /// value will be considered as follows:
        /// 8 7 6
        /// 5 x 4
        /// 3 2 1
        /// Where x is the center tile of the direction.
        /// 
        /// If the method should return the outer tile in the given direction, the value will be interpreted as follows:
        /// 8  7  6
        /// 
        /// 5  x  4
        /// 
        /// 3  2  1
        /// Where x is the center tile of the direction.
        /// </summary>
        /// <param name="direction">The direction to return as an enum.</param>
        /// <param name="isInnerDirection">A boolean indicating whether to return the inner or outer direction value.</param>
        /// <returns>The opposite Direction enum of the given direction and inner/outer circle.</returns>
        private Direction GetOppositeDirection(short direction, bool isInnerDirection)
        {
            switch (direction)
            {
                case 1:
                    return isInnerDirection ? Direction.InnerBottomRight : Direction.OuterBottomRight;
                case 2:
                    return isInnerDirection ? Direction.InnerBottom : Direction.OuterBottom;
                case 3:
                    return isInnerDirection ? Direction.InnerBottomLeft : Direction.OuterBottomLeft;
                case 4:
                    return isInnerDirection ? Direction.InnerRight : Direction.OuterRight;
                case 5:
                    return isInnerDirection ? Direction.InnerLeft : Direction.OuterLeft;
                case 6:
                    return isInnerDirection ? Direction.InnerTopRight : Direction.OuterTopRight;
                case 7:
                    return isInnerDirection ? Direction.InnerTop : Direction.OuterTop;
                case 8:
                    return isInnerDirection ? Direction.InnerTopLeft : Direction.OuterTopLeft;
                default:
                    return Direction.Center;
            }
        }

        /// <summary>
        /// Counts the amount of tiles with the TileState of the tile with the index centerIndex in the tilelist.
        /// It counts the tiles in the direction passed.
        /// </summary>
        /// <param name="direction">The direction to look for the row.</param>
        /// <param name="centerIndex">The index of the tile to start from. This index will be the last index once the method is finished.</param>
        /// <returns>The amount of tiles with the same TileState in the direction passed.</returns>
        private short CountInARow(Direction direction, ref short centerIndex)
        {
            short rowCounter = 0;
            Tile tile = tiles[centerIndex];
            bool stateIsSame = true;

            if (tile == null)
            {
                return rowCounter;
            }

            while (stateIsSame && rowCounter <= 4)
            {
                rowCounter++;
                centerIndex += (short)direction;

                Tile nextTile = tiles[centerIndex];
                if (nextTile == null)
                {
                    return rowCounter;
                }

                stateIsSame = tile.State == tiles[centerIndex].State;
            }

            return rowCounter;
        }

        /// <summary>
        /// Calculates all the possible moves for the current player and current board situation
        /// </summary>
        public List<Move> GetPossibleMoves()
        {
            // If we already generated the moves, don't generate them again but just return them.
            if (possibleMoves != null)
            {
                return possibleMoves;
            }

            // The possible moves will be stored in case this board already generated them before.
            possibleMoves = new List<Move>();
            Move move;

            // If we are in the first phase of the game (placing the 12 cones without changing the board), we have
            // to generate different moves.
            if (numberOfConesOnBoard < 12)
            {
                for (short index = 154; index < 222; index++)
                {
                    // If the index goes out of bounds, too far to the right, go to the next line.
                    if (index % Engine.width > 11)
                    {
                        index -= 5;
                        index += Engine.width;
                    }

                    // If this tile is an empty tile (and not a null tile), try to add the move.
                    if (tiles[index] != null && tiles[index].State == TileState.Empty)
                    {
                        // Create the move.
                        move = new Move(-1, index, -1); ;                            // Check if the move is valid.
                        if (Rules.IsValidMove(this, move, false))
                        {
                            // Add the move if it is valid.
                            possibleMoves.Add(move);
                        }
                    }
                }
            }
            else
            {
                for (short index = 0; index < tiles.Count; index++)
                {
                    // If the tile we picked is not an occupied tile.
                    if (tiles[index] == null || tiles[index].State == TileState.Empty || !IsPlayerTile(index, CurrentPlayer))
                    {
                        continue;
                    }

                    Neighbours adjacentTile = GetAdjacentIndices(index);

                    // These are normal moves instead of jumps.
                    foreach (short innerNeighbour in adjacentTile.InnerNeighbours)
                    {
                        if (tiles[innerNeighbour] == null)
                        {
                            foreach (short movableTile in movableTiles)
                            {
                                move = new Move(index, innerNeighbour, movableTile);
                                if (Rules.IsValidMove(this, move, false))
                                {
                                    possibleMoves.Add(move);
                                }
                            }
                        }
                        else if (tiles[innerNeighbour].State == TileState.Empty)
                        {
                            move = new Move(index, innerNeighbour, -1);
                            if (Rules.IsValidMove(this, move, false))
                            {
                                possibleMoves.Add(move);
                            }
                        }
                    }
                    foreach (short outerNeighbour in adjacentTile.OuterNeighbours)
                    {
                        if (tiles[outerNeighbour] == null)
                        {
                            foreach (short movableTile in movableTiles)
                            {
                                move = new Move(index, outerNeighbour, movableTile);
                                if (Rules.IsValidMove(this, move, false))
                                {
                                    possibleMoves.Add(move);
                                }
                            }
                        }
                        else if (tiles[outerNeighbour].State == TileState.Empty)
                        {
                            move = new Move(index, outerNeighbour, -1);
                            if (Rules.IsValidMove(this, move, false))
                            {
                                possibleMoves.Add(move);
                            }
                        }
                    }
                }
            }

            return possibleMoves;
        }

        #region Do and UnDo Moves

        // Will not check for valid moves!
        // One should seperately check Rules.IsValidMove first
        public void DoMove(Move move)
        {
            bool hasMoved = false;

            // We have to recalculate the possible moves if we change the board.
            possibleMoves = null;

            // doing a general move, after all cones have been set on the board first.
            if (move.PositionFrom > -1 && move.PositionTo > -1)
            {
                zobristHash = TranspositionTable.Instance.GetZobristHash(this, move);
                
                // Flip if necessairy
                if(ShouldFlip(move.PositionFrom, move.PositionTo))
                    Tiles[ move.PositionFrom ].State = FlipCone( Tiles[ move.PositionFrom ].State );

                if (move.TilePositionToMove != -1)
                {
                    SetTile(move.PositionTo, this.tiles[move.TilePositionToMove]);
                    SetTile(move.TilePositionToMove, null);

                    UpdatePlayTiles(move.PositionTo, move.TilePositionToMove);
                }

                UpdateMovableTiles();

                this.tiles[move.PositionTo].State = this.tiles[move.PositionFrom].State;
                this.tiles[move.PositionFrom].State = TileState.Empty;

                hasMoved = true;
            }
            // doing a first move, when all the cones have to be set on the board first
            else if (move.PositionFrom == -1 && move.PositionTo > -1)
            {
                zobristHash = TranspositionTable.Instance.GetZobristHash(this, move);

                // set the right cone on the move.PositionTo depending on which players turn it is
                if (this.currentPlayer == Player.Red)
                {
                    SetTile(move.PositionTo, new Tile() { State = TileState.RedNormal });
                }
                if (this.currentPlayer == Player.White)
                {
                    SetTile(move.PositionTo, new Tile() { State = TileState.WhiteNormal });
                }

                hasMoved = true;
            }
            else if(move.PositionTo ==-1 && move.PositionFrom==-1 && move.TilePositionToMove==-1)
            {
                this.moveHistory.Push(move);
            }

            if (hasMoved)
            {
                this.currentPlayer = this.OppositePlayer;
                this.moveHistory.Push(move);
            }
        }

        public Move UnDoMove()
        {
            Move move = this.MoveHistory.Pop();

            // We have to recalculate the possible moves if we change the board.
            possibleMoves = null;

            if (move.PositionFrom != -1)
            {
                // DETERMINE if should flip
                bool shouldFlip = ShouldFlip(move.PositionTo, move.PositionFrom);

                this.tiles[move.PositionFrom].State = this.tiles[move.PositionTo].State;

                // Flip if determined
                if(shouldFlip)
                    Tiles[ move.PositionFrom ].State = FlipCone( Tiles[ move.PositionFrom ].State );
            }

            SetTile(move.PositionTo, new Tile() { State = TileState.Empty });

            if (move.TilePositionToMove != -1)
            {
                SetTile(move.TilePositionToMove, this.tiles[move.PositionTo]);
                SetTile(move.PositionTo, null);
                UpdatePlayTiles( move.TilePositionToMove, move.PositionTo );
            }

            UpdateMovableTiles();

            if (move.PositionTo > -1 || move.PositionFrom > -1 || move.TilePositionToMove > -1)
            {
                currentPlayer = OppositePlayer;
            }

            zobristHash = TranspositionTable.Instance.GetZobristHash(this, move);

            return move;
        }

        /// <summary>
        /// Calculates and returns whether or not a cone should be flipped when we move from @tileFrom to @tileTo.
        /// Public for Engine.
        /// </summary>
        /// <param name="tileFrom">The tilePosition from where you move</param>
        /// <param name="tileTo">The tilePosition to where you move</param>
        /// <returns>Whether or not the cone should be flipped when executing this move</returns>
        public bool ShouldFlip( short tileFrom, short tileTo )
        {
            List<short> outerIndices = GetAdjacentIndices( tileFrom ).OuterNeighbours;
            foreach ( short s in outerIndices )
            {
                if ( s == tileTo )
                {
                    hasToBeFlipped = true;
                    return true;
                }
            }
            hasToBeFlipped = false;
            return false;
        }

        public TileState FlipCone(TileState cone)
        {
            switch (cone)
            {
                case TileState.RedNormal:
                    return TileState.RedUpsideDown;
                case TileState.RedUpsideDown:
                    return TileState.RedNormal;
                case TileState.WhiteNormal:
                    return TileState.WhiteUpsideDown;
                case TileState.WhiteUpsideDown:
                    return TileState.WhiteNormal;
                default:
                    return TileState.Empty;
            }
        }

        public Stack<Move> MoveHistory
        {
            get { return this.moveHistory; }
            set { this.moveHistory = value; }
        }

        #endregion

        private void UpdatePlayTiles(short add, short delete)
        {
            playTiles.Remove(delete);
            playTiles.Add(add);
        }

        private void UpdateMovableTiles()
        {
            movableTiles.Clear();
            foreach (short i in playTiles)
            {
                if (IsCornerTile(i))
                    movableTiles.Add(i);
            }
        }

        public void CalculatePlayAndMovableTiles()
        {
            movableTiles.Clear();
            playTiles.Clear();
            for (short i = 0; i < tiles.Count; i++)
            {
                if (tiles[i] == null) continue;

                playTiles.Add(i);

                if (IsCornerTile(i))
                    movableTiles.Add(i);
            }
        }

        /// <summary>
        /// Checks if any player has 4 in a row, which would end the game. If the game is at an end,
        /// the method wil return true.
        /// </summary>
        /// <returns>A boolean indicating whether the game has ended.</returns>
        public bool IsTerminal()
        {
            foreach (short index in playTiles)
            {
                Tile tile = tiles[index];

                if (tile.State == TileState.RedUpsideDown || tile.State == TileState.WhiteUpsideDown)
                {
                    for (short directionIndex = 5; directionIndex < 9; directionIndex++)
                    {
                        Direction direction = GetDirection(directionIndex, true);
                        short newIndex = index;
                        short rowCounter = CountInARow(direction, ref newIndex);

                        if (rowCounter >= 4)
                        {
                            return true;
                        }
                    }
                }
            }

            return false;
        }

        /// <summary>
        /// Gives a schematic ANSII representation of this board situation. This can be used for easy debugging. It also
        /// provides row and column counters.
        /// </summary>
        /// <returns>The ANSII overview representation of this situation.</returns>
        public override string ToString()
        {
            short index = 0;

            // Create a stringbuilder with a column of numbers an the 1st row count. All tile information and further
            // row information will be appended in the foreach loop.
            StringBuilder stringBuilder = new StringBuilder(string.Empty);
            stringBuilder.Append("  1  2  3  4  5  6  7  8  9  10 11 12 13 14 15 16 17 18 19 20 21 ");
            stringBuilder.Append(Environment.NewLine);
            stringBuilder.Append("1 ");

            // Loop through all tiles and append the tile information and rownumber.
            foreach (Tile tile in tiles)
            {
                // If we are at the virtual end of the row, go to the next line and append the row number.
                if (index != 0 && index % Engine.width == 0)
                {
                    stringBuilder.Append(Environment.NewLine);

                    // Append a rownumber (and a space if the rownumber is lower than 10).
                    short rowCount = (short)((index / Engine.width) + 1);
                    stringBuilder.Append(rowCount < 10 ? rowCount.ToString() + " " : rowCount.ToString());
                }

                // Append tile information.
                if (tile == null)
                {
                    stringBuilder.Append("   ");
                }
                else
                {
                    switch (tile.State)
                    {
                        case TileState.Empty:
                            stringBuilder.Append("[ ]");
                            break;
                        case TileState.RedNormal:
                            stringBuilder.Append("[r]");
                            break;
                        case TileState.RedUpsideDown:
                            stringBuilder.Append("[R]");
                            break;
                        case TileState.WhiteNormal:
                            stringBuilder.Append("[w]");
                            break;
                        case TileState.WhiteUpsideDown:
                            stringBuilder.Append("[W]");
                            break;
                    }
                }

                index++;
            }

            return stringBuilder.ToString();
        }

        /// <summary>
        /// Gives a detailed ANSII representation of this board situation. This can be used for easy debugging. It also
        /// provides indices of each position in the tile array.
        /// </summary>
        /// <returns>The detailed ANSII representation of this situation.</returns>
        public string ToStringDetailed()
        {
            short index = 0;

            // Create a stringbuilder. All tile information and further will be appended in the foreach loop.
            StringBuilder stringBuilder = new StringBuilder(string.Empty);

            // Loop through all tiles and append the tile information.
            foreach (Tile tile in tiles)
            {
                // If we are at the virtual end of the row, go to the next line.
                if (index != 0 && index % Engine.width == 0)
                {
                    stringBuilder.Append(Environment.NewLine);
                }

                String indexString = index + "";

                if (index < 10)
                {
                    indexString = " " + indexString;
                }
                if (index < 100)
                {
                    indexString = " " + indexString;
                }

                // Append tile information.
                if (tile == null)
                {
                    stringBuilder.Append("   " + indexString + " ");
                }
                else
                {
                    switch (tile.State)
                    {
                        case TileState.Empty:
                            stringBuilder.Append("[  " + indexString + "]");
                            break;
                        case TileState.RedNormal:
                            stringBuilder.Append("[r " + indexString + "]");
                            break;
                        case TileState.RedUpsideDown:
                            stringBuilder.Append("[R " + indexString + "]");
                            break;
                        case TileState.WhiteNormal:
                            stringBuilder.Append("[w " + indexString + "]");
                            break;
                        case TileState.WhiteUpsideDown:
                            stringBuilder.Append("[W " + indexString + "]");
                            break;
                    }
                }

                index++;
            }

            return stringBuilder.ToString();
        }

        /// <summary>
        /// Determines if the position at the board at the given index is an availeble tile for a 'move-tile' action.
        /// Only VALID cornerTiles are considered cornerTiles (ignore the litteral meaning of cornerTile)
        /// </summary>
        /// <returns>Boolean: Whether or not a position on the board is a tile that can be moved.</returns>
        public bool IsCornerTile(short index)
        {
            if (Tiles[index] == null)
                return false;

            if(Tiles[index].State != TileState.Empty)
                return false;

            // 3 sides always cornerTile
            if (Tiles[index].FreeSides == 3)
                return true;

            // If not 2 freesides, it's not a cornerTile
            if (Tiles[index].FreeSides != 2)
                return false;

            // Determine free sides
            short topNeighbour = GetNeighbour(index, Direction.InnerTop);
            short leftNeighbour = GetNeighbour(index, Direction.InnerLeft);
            short rightNeighbour = GetNeighbour(index, Direction.InnerRight);
            short bottomNeighbour = GetNeighbour(index, Direction.InnerBottom);

            // != null i.e. there is a Tile
            bool hasTopNeighbour = topNeighbour >= 0 && Tiles[topNeighbour] != null;
            bool hasLeftNeighbour = leftNeighbour >= 0 && Tiles[leftNeighbour] != null;
            bool hasRightNeighbour = rightNeighbour >= 0 && Tiles[rightNeighbour] != null;
            bool hasBottomNeighbour = bottomNeighbour >= 0 && Tiles[bottomNeighbour] != null;

            // If the tile has 2 neighbours in opposite direction, it is not a cornerTile (the remaining 2 freeSides will be opposite as well)
            if ((hasTopNeighbour && hasBottomNeighbour) || (hasLeftNeighbour && hasRightNeighbour))
                return false;

            // To speeden up: if the picked tile's neighbours are neighbours, then it is a (VALID) cornerTile
            if (hasTopNeighbour && hasLeftNeighbour)
            {
                // Check if the upper neighbour from my left neighbour equals the left neighbour from my upper neighbour
                // And there is a tile at that position
                //
                // ? a  a=upperneighbour
                // b c  b=leftneighbour, c=index/currentTile
                short neighbour = GetNeighbour(topNeighbour, Direction.InnerLeft);  // Left neigbour from a
                if (neighbour != -1 && tiles[neighbour] != null && neighbour == GetNeighbour(leftNeighbour, Direction.InnerTop))
                    return true;
            }
            else if (hasTopNeighbour && hasRightNeighbour)
            {
                short neighbour = GetNeighbour(topNeighbour, Direction.InnerRight); // Right neighbour from a.. (b on other side)
                if (neighbour != -1 && tiles[neighbour] != null && neighbour == GetNeighbour(rightNeighbour, Direction.InnerTop))
                    return true;
            }
            else if (hasBottomNeighbour && hasLeftNeighbour)
            {
                short neighbour = GetNeighbour(bottomNeighbour, Direction.InnerLeft);
                if (neighbour != -1 && tiles[neighbour] != null && neighbour == GetNeighbour(leftNeighbour, Direction.InnerBottom))
                    return true;
            }
            else if (hasBottomNeighbour && hasRightNeighbour)
            {
                short neighbour = GetNeighbour(bottomNeighbour, Direction.InnerRight);
                if (neighbour != -1 && tiles[neighbour] != null && neighbour == GetNeighbour(rightNeighbour, Direction.InnerBottom))
                    return true;
            }

            // If removing this tile seperates the board in two parts, it is not a corner tile
            // Pick a tile (doesn't matter which one)
            // Verify that all tiles in the list are his neighbours or his neigbours' neighbours,
            // or his neighbours' neighbours' neighbours etc.. 

            // Get a 'random' tile (a neighbour from tile to be checked)
            short firstTile = (hasTopNeighbour)
                            ? topNeighbour
                            : (hasRightNeighbour)
                                  ? rightNeighbour
                                  : (hasBottomNeighbour) ? bottomNeighbour : leftNeighbour;

            List<short> neighbours = new List<short>();
            neighbours.Add(firstTile);

            short checkedIndex = 0; // Use this variable to keep track of which neighbours' neighbours we already added to "List<short> neighbours"
            for (short ss = 0; ss < 19; ss++)
            {
                if (neighbours.Count == 19)
                    break;

                List<short> tempNeighbours = new List<short>();
                tempNeighbours.AddRange(neighbours);

                for (short s = checkedIndex; s < tempNeighbours.Count; s++)
                {
                    short addNeighboursFromThisTile = tempNeighbours[s];

                    // add DIRECT indices from addNeighboursFromThisTile (except index)
                    topNeighbour = GetNeighbour(addNeighboursFromThisTile, Direction.InnerTop);
                    rightNeighbour = GetNeighbour(addNeighboursFromThisTile, Direction.InnerRight);
                    bottomNeighbour = GetNeighbour(addNeighboursFromThisTile, Direction.InnerBottom);
                    leftNeighbour = GetNeighbour(addNeighboursFromThisTile, Direction.InnerLeft);
                    if (topNeighbour != index && tiles[topNeighbour] != null && !neighbours.Contains(topNeighbour))
                        neighbours.Add(topNeighbour);
                    if (rightNeighbour != index && tiles[rightNeighbour] != null && !neighbours.Contains(rightNeighbour))
                        neighbours.Add(rightNeighbour);
                    if (bottomNeighbour != index && tiles[bottomNeighbour] != null && !neighbours.Contains(bottomNeighbour))
                        neighbours.Add(bottomNeighbour);
                    if (leftNeighbour != index && tiles[leftNeighbour] != null && !neighbours.Contains(leftNeighbour))
                        neighbours.Add(leftNeighbour);

                    checkedIndex++;
                }
            }

            if (neighbours.Count == 19)
            {
                return true;
            }
            else
            {
                return false;
            }
        }
    }
}